SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
11.8 KB · 234 lines tsx
Raw Blame History
1import Link from "next/link";2import { notFound } from "next/navigation";3import { ArrowLeft } from "lucide-react";4import { requireAdmin } from "@/lib/session";5import { getRequestDetail } from "@/lib/queries/admin";6import { formatBytes, formatDate, formatMs, formatUsd } from "@/lib/format";7import { PageHeader } from "@/components/ui/page-header";8import { Badge, StatusBadge } from "@/components/ui/badge";9import { Button } from "@/components/ui/button";10import { Stat, StatGrid } from "@/components/ui/stat";11import { CodeBlock } from "@/components/ui/code-block";12import { CopyButton } from "@/components/ui/copy-button";13import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";14import { KV, MarginText, Mono, NetworkBadge, Panel, PlanBadge, ProviderBadge, numCell } from "@/components/admin/primitives";1516export const dynamic = "force-dynamic";1718export default async function AdminRequestDetail({ params }: { params: Promise<{ id: string }> }) {19  await requireAdmin();20  const { id } = await params;21  const d = await getRequestDetail(id);22  if (!d) notFound();23  const { req } = d;24  const margin = req.priceUsd - req.costUsd;2526  return (27    <>28      <div className="mb-4">29        <Button asChild variant="ghost" size="sm" className="-ml-2">30          <Link href="/admin/requests">31            <ArrowLeft className="size-3.5" /> Requests32          </Link>33        </Button>34      </div>35      <PageHeader36        eyebrow="Request"37        title={38          <span className="flex flex-wrap items-center gap-2">39            <Mono className="text-[18px]">{req.id}</Mono>40            <CopyButton value={req.id} size="icon-sm" className="size-6" />41            <StatusBadge status={req.status} />42            {req.errorCode ? <Badge variant="danger">{req.errorCode}</Badge> : null}43            {req.cached ? <Badge variant="info">cached</Badge> : null}44          </span>45        }46        description={47          <span className="break-all">48            {req.method} {req.url}49          </span>50        }51        actions={52          <>53            {d.org ? (54              <Button asChild variant="outline" size="sm">55                <Link href={`/admin/organizations/${d.org.id}`}>Organization</Link>56              </Button>57            ) : null}58            <Button asChild variant="outline" size="sm">59              <Link href={`/admin/domains/${encodeURIComponent(req.domain)}`}>Domain profile</Link>60            </Button>61          </>62        }63      />6465      <StatGrid cols={6} className="mb-6">66        <Stat label="HTTP status" value={req.httpStatus ?? "—"} />67        <Stat label="Attempts" value={req.attempts} />68        <Stat label="Latency" value={formatMs(req.latencyMs)} />69        <Stat label="Bytes in / out" value={`${formatBytes(req.bytesIn)} / ${formatBytes(req.bytesOut)}`} />70        <Stat label="Upstream cost → price" value={`${formatUsd(req.costUsd, true)} → ${formatUsd(req.priceUsd, true)}`} />71        <Stat label="Margin" value={<MarginText value={margin} pct={req.priceUsd > 0 ? (margin / req.priceUsd) * 100 : null} />} />72      </StatGrid>7374      <div className="grid gap-6 lg:grid-cols-2">75        <Panel title="Request">76          <KV77            cols={2}78            items={[79              { label: "Organization", value: d.org ? <span className="inline-flex items-center gap-2"><Link href={`/admin/organizations/${d.org.id}`} className="hover:underline">{d.org.name}</Link><PlanBadge plan={d.org.plan} />{d.org.providerVisibility ? <Badge variant="warning">provider debug on</Badge> : null}</span> : req.organizationId },80              { label: "Project", value: d.project ? `${d.project.name} (${d.project.environment})` : req.projectId },81              { label: "API key", value: d.key ? <span>{d.key.name} <Mono className="text-fg-muted">{d.key.keyPrefix}…{d.key.last4}</Mono> <StatusBadge status={d.key.mode} /></span> : req.source === "playground" ? "— (playground)" : "—" },82              { label: "Source", value: req.source },83              { label: "URL", value: <span className="break-all">{req.url}</span>, mono: true, span: true },84              { label: "Final URL", value: req.finalUrl ? <span className="break-all">{req.finalUrl}</span> : "—", mono: true, span: true },85              { label: "Domain", value: req.domain, mono: true },86              { label: "Method", value: req.method, mono: true },87              { label: "Requested network", value: req.requestedNetwork },88              { label: "Resolved network", value: <NetworkBadge network={req.network} /> },89              { label: "Geo", value: [req.country, req.region, req.city].filter(Boolean).join(" / ") || "—", mono: true },90              { label: "Session", value: req.sessionId ?? "—", mono: true },91              { label: "Browser", value: req.browser ? "requested" : "no" },92              { label: "Format", value: req.format },93              { label: "Client IP", value: req.clientIp ?? "—", mono: true },94              { label: "User agent", value: req.userAgent ?? "—", span: true },95              { label: "Created", value: formatDate(req.createdAt) },96              { label: "Completed", value: req.completedAt ? formatDate(req.completedAt) : "—" },97              { label: "Error message", value: req.errorMessage ?? "—", span: true },98            ]}99          />100        </Panel>101        <div className="grid gap-6">102          <Panel title="Timing" description="Milliseconds, as recorded by the gateway.">103            {req.timing && Object.keys(req.timing).length ? (104              <KV cols={3} items={Object.entries(req.timing).map(([k, v]) => ({ label: k, value: formatMs(v), mono: true }))} />105            ) : (106              <p className="text-[13px] text-fg-muted">No timing recorded.</p>107            )}108          </Panel>109          {d.session ? (110            <Panel title="Proxy session" description="Upstream sticky session used by this request.">111              <KV112                cols={2}113                items={[114                  { label: "Session", value: d.session.id, mono: true },115                  { label: "Provider", value: <ProviderBadge id={d.session.provider} /> },116                  { label: "Network", value: <NetworkBadge network={d.session.network} /> },117                  { label: "Status", value: <StatusBadge status={d.session.status} /> },118                  { label: "Requests", value: d.session.requestCount },119                  { label: "Expires", value: formatDate(d.session.expiresAt) },120                ]}121              />122            </Panel>123          ) : null}124          <Panel title="Usage events" flush>125            <Table>126              <TableHeader>127                <TableRow>128                  <TableHead>Metric</TableHead>129                  <TableHead className="text-right">Quantity</TableHead>130                  <TableHead className="text-right">Price</TableHead>131                  <TableHead className="text-right">Upstream</TableHead>132                </TableRow>133              </TableHeader>134              <TableBody>135                {d.usage.length === 0 ? (136                  <TableEmpty colSpan={4}>No usage events (free-tier or failed before metering).</TableEmpty>137                ) : (138                  d.usage.map((u) => (139                    <TableRow key={u.id}>140                      <TableCell>141                        <Mono>{u.metric}</Mono>142                      </TableCell>143                      <TableCell className={numCell}>144                        {u.unit === "bytes" ? formatBytes(u.quantity) : `${u.quantity} ${u.unit}`}145                      </TableCell>146                      <TableCell className={numCell}>{formatUsd(u.costUsd, true)}</TableCell>147                      <TableCell className={numCell}>{formatUsd(u.upstreamCostUsd, true)}</TableCell>148                    </TableRow>149                  ))150                )}151              </TableBody>152            </Table>153          </Panel>154        </div>155      </div>156157      <Panel title="Attempts" description="Every upstream attempt with provider, routing score and internal error detail. Never shown to customers." flush className="mt-6">158        <Table>159          <TableHeader>160            <TableRow>161              <TableHead>#</TableHead>162              <TableHead>Provider</TableHead>163              <TableHead>Network</TableHead>164              <TableHead>Geo</TableHead>165              <TableHead>Outcome</TableHead>166              <TableHead className="text-right">HTTP</TableHead>167              <TableHead>Error / block reason</TableHead>168              <TableHead className="text-right">Score</TableHead>169              <TableHead className="text-right">Duration</TableHead>170              <TableHead className="text-right">Bytes</TableHead>171              <TableHead className="text-right">$/GB</TableHead>172              <TableHead className="text-right">Cost</TableHead>173            </TableRow>174          </TableHeader>175          <TableBody>176            {d.attempts.length === 0 ? (177              <TableEmpty colSpan={12}>No attempts recorded (request rejected before routing).</TableEmpty>178            ) : (179              d.attempts.map((a) => (180                <TableRow key={a.id}>181                  <TableCell className={numCell}>{a.attemptNo}</TableCell>182                  <TableCell>183                    <ProviderBadge id={a.provider} />184                  </TableCell>185                  <TableCell>186                    <NetworkBadge network={a.network} />187                  </TableCell>188                  <TableCell>189                    <Mono>{a.country ?? "—"}</Mono>190                    {a.sessionKey ? <div className="font-mono text-[11px] text-fg-subtle">sticky</div> : null}191                  </TableCell>192                  <TableCell>193                    <StatusBadge status={a.outcome} />194                  </TableCell>195                  <TableCell className={numCell}>{a.httpStatus ?? "—"}</TableCell>196                  <TableCell className="max-w-[320px]">197                    {a.errorCode ? <div className="font-mono text-[12px] text-danger">{a.errorCode}</div> : null}198                    {a.blockReason ? <div className="text-[12px] text-fg-muted">block: {a.blockReason}</div> : null}199                    {a.errorDetail ? (200                      <div className="truncate font-mono text-[11.5px] text-fg-subtle" title={a.errorDetail}>201                        {a.errorDetail}202                      </div>203                    ) : null}204                    {!a.errorCode && !a.blockReason && !a.errorDetail ? <span className="text-fg-subtle">—</span> : null}205                  </TableCell>206                  <TableCell className={numCell}>{a.routingScore !== null ? a.routingScore.toFixed(3) : "—"}</TableCell>207                  <TableCell className={numCell}>{formatMs(a.durationMs)}</TableCell>208                  <TableCell className={numCell}>{formatBytes(a.bytesIn + a.bytesOut)}</TableCell>209                  <TableCell className={numCell}>{a.unitPricePerGb ? `$${a.unitPricePerGb}` : "—"}</TableCell>210                  <TableCell className={numCell}>{formatUsd(a.costUsd, true)}</TableCell>211                </TableRow>212              ))213            )}214          </TableBody>215        </Table>216      </Panel>217218      <div className="mt-6 grid gap-6 lg:grid-cols-2">219        <Panel title="Request headers" description="Sensitive headers are redacted at ingestion.">220          <CodeBlock code={JSON.stringify(req.requestHeaders ?? {}, null, 2)} lang="json" maxHeight={320} />221        </Panel>222        <Panel title="Response headers">223          <CodeBlock code={JSON.stringify(req.responseHeaders ?? {}, null, 2)} lang="json" maxHeight={320} />224        </Panel>225      </div>226      {d.attempts.some((a) => a.timing) ? (227        <Panel title="Attempt timing (raw)" className="mt-6">228          <CodeBlock code={JSON.stringify(d.attempts.map((a) => ({ attempt: a.attemptNo, provider: a.provider, timing: a.timing })), null, 2)} lang="json" maxHeight={320} />229        </Panel>230      ) : null}231    </>232  );233}234